﻿//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Common Core Extensions
//===============================================================================

using System;
using System.IO;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;

namespace EntLibContrib.Common.Configuration.Design
{
	/// <summary>
	/// Assembly Loader and resolver
	/// </summary>
	public sealed class AssemblyLoader : IDisposable
	{
		#region Fields
		private ResolveEventHandler assemblyResolveHandler;
    private string copiedAssemblyPath;
    private string referenceDirectory;
    private static bool resolving;
		#endregion

		#region Properties
		/// <summary>
		/// Gets the copied assembly path.
		/// </summary>
		/// <value>The copied assembly path.</value>
		public string CopiedAssemblyPath
		{
			get { return this.copiedAssemblyPath; }
		}
		#endregion

		#region Construction
		/// <summary>
		/// Initializes a new instance of the <see cref="AssemblyLoader"/> class.
		/// </summary>
		/// <param name="assemblyPath">The assembly path.</param>
		/// <param name="referenceDirectory">The reference directory.</param>
		public AssemblyLoader(string assemblyPath, string referenceDirectory)
    {
			// try to create a copy of the assembly
			this.copiedAssemblyPath = CreateCopyOfAssembly(assemblyPath);
      this.referenceDirectory = referenceDirectory;
			// decorate the current application domain with a resolve event handler
			this.assemblyResolveHandler = new ResolveEventHandler(this.AssemblyResolve);
      AppDomain.CurrentDomain.AssemblyResolve += this.assemblyResolveHandler;
      AppDomain.CurrentDomain.TypeResolve += this.assemblyResolveHandler;
    }
		#endregion

		#region Event handler
		/// <summary>
		/// Resolve event handler to be applied if the resolution of an assembly or type fails.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="System.ResolveEventArgs"/> instance containing the event data.</param>
		/// <returns></returns>
		[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")]
		private Assembly AssemblyResolve(object sender, ResolveEventArgs args)
    {
      if (resolving)
        return null;

			try
      {
        resolving = true;
        AssemblyName name = null;
        try
        {
          name = new AssemblyName(args.Name);
        }
        catch
        {
          return null;
        }

				string assemblyName = Path.Combine(this.referenceDirectory, name.Name) + ".dll";
				if (File.Exists(assemblyName))
					assemblyName = CreateCopyOfAssembly(assemblyName);
				else
				{
					assemblyName = Path.Combine(this.referenceDirectory, name.Name) + ".exe";
					if (File.Exists(assemblyName))
						assemblyName = CreateCopyOfAssembly(assemblyName);
					else
						return null;
				}

				try
        {
					return Assembly.LoadFrom(assemblyName);
        }
        catch
        {
          return null;
        }
      }
      finally
      {
        resolving = false;
      }
    }
		#endregion

		#region Public Methods
		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			AppDomain.CurrentDomain.AssemblyResolve -= this.assemblyResolveHandler;
			AppDomain.CurrentDomain.TypeResolve -= this.assemblyResolveHandler;
		}
		#endregion

		#region Private Methods
		/// <summary>
		/// Creates a copy of an assembly.
		/// </summary>
		/// <param name="originalFilename">The original filename.</param>
		/// <returns>the copied assembly filename</returns>
		private static string CreateCopyOfAssembly(string originalFilename)
    {
			string filename = originalFilename;
      try
      {
        string tempFileName = Path.GetTempFileName();
        File.Copy(originalFilename, tempFileName, true);
				filename = tempFileName;
      }
      catch (IOException)
      {
				// If we cannot copy the file to a temp directory.
				// We will use the original assembly file instead. The downside
				// of using the original assembly file is that you cannot 
				// update the assembly containing the type
				// until you close the Configuration Manager tool.
			}
			return filename;
    }
		#endregion
	}
}
